/*
 *  ------------------------------------------------------------------
 *  Copyright © 2017 Hangzhou DtDream Technology Co.,Lt d. All rights reserved.
 *  ------------------------------------------------------------------
 *              Product: EMR
 *    Module Name: DataTrain
 *      Date Created: 2017-10-23
 *         Description:
 *  ------------------------------------------------------------------
 *  Modification History
 *  DATE            Name           Description
 *  ------------------------------------------------------------------
 *  2017-10-23     XZ 209
 *  ------------------------------------------------------------------
 */

package org.xukai.remoting.sdk.rpc.transfer;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.apache.http.HttpStatus.SC_BAD_GATEWAY;
import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE;
import static org.apache.http.HttpStatus.SC_GATEWAY_TIMEOUT;

public class ControlChannel implements AutoCloseable {

    private static final Logger LOG = LoggerFactory.getLogger(ControlChannel.class);

    private static final int HTTP_SO_TIMEOUT_MILLISECONDS = 6000 * 1000;
    private static final RequestConfig REQUEST_CONFIG = RequestConfig.custom().setAuthenticationEnabled(false)
            .setSocketTimeout(HTTP_SO_TIMEOUT_MILLISECONDS).build();

    private JavaSerializer deserializer = new JavaSerializer(
            Arrays.asList(TransmissionException.class.getName(), TransmissionException.Error.class.getName()),
            Collections.singletonList("java"));

    private URI baseUrl;
    private HttpClient httpClient;
    private static final String HTTP_PROTO = "http://";

    public ControlChannel(String host, int port) {
        try {
            this.baseUrl = new URI(HTTP_PROTO + host + ":" + port);
            initHttpClient();
        } catch (URISyntaxException e) {
            throw new Error(e);
        }
    }

    private void initHttpClient() {
        HttpClientBuilder builder = HttpClientBuilder.create();
        builder.setRetryHandler(new DefaultHttpRequestRetryHandler());
        httpClient = builder.build();
    }

    public Map<String, Object> get(String path, Map<String, Object> params) throws IOException, TransmissionException {
        return execute(HttpGet.class, path, params);
    }

    public Map<String, Object> post(String path, Map<String, Object> params) throws IOException, TransmissionException {
        return execute(HttpPost.class, path, params);
    }

    public Map<String, Object> execute(Class<? extends HttpRequestBase> method, String path, Map<String, Object> params)
            throws TransmissionException, IOException {

        HttpRequestBase request;
        try {
            List<NameValuePair> paramsList = new ArrayList<>();
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                Object value = entry.getValue();
                if (null != value) {
                    paramsList.add(new BasicNameValuePair(entry.getKey(), value.toString()));
                }
            }

            request = method.newInstance();
            request.setURI(new URIBuilder(baseUrl).setPath(path).setParameters(paramsList).build());
            request.setConfig(REQUEST_CONFIG);
        } catch (URISyntaxException | InstantiationException | IllegalAccessException e) {
            throw new Error(e);
        }

        HttpResponse response = httpClient.execute(request);

        HttpEntity responseEntity = response.getEntity();
        try (InputStream in = responseEntity.getContent()) {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode >= 200 && statusCode < 300) {
                return JSONObject.parseObject(IOUtils.toString(in, "utf-8"), Map.class);
            } else {
                LOG.warn("Post error, status " + statusCode);
                if (SC_BAD_GATEWAY == statusCode || SC_SERVICE_UNAVAILABLE == statusCode || SC_GATEWAY_TIMEOUT == statusCode) {
                    throw new IOException("Server unavailable");
                }
                Throwable serverSideException = deserializer.deserialize(in);
                if (serverSideException instanceof TransmissionException) {
                    TransmissionException transmissionException = (TransmissionException) serverSideException;
                    throw new TransmissionException(transmissionException.getError(), transmissionException.getDetail());
                } else {
                    throw new TransmissionException(EXCEPTION_SDK_SERVER, serverSideException);
                }
            }
        } catch (ClassNotFoundException e) {
            throw new TransmissionException(EXCEPTION_SDK_PARSE, e);
        }
    }

    public void close() {
        try {
            if (httpClient instanceof CloseableHttpClient) {
                ((CloseableHttpClient) httpClient).close();
            }
        } catch (IOException e) {
            LOG.warn("Error occurred while closing HttpClient");
        }
    }
}
